Duet Package Tutorial: Single Person Analysis
This tutorial demonstrates a complete workflow for analyzing OpenPose
data from a single individual using the
duet R package.
1. Setup and Installation
Install Required Packages
The chunk below will install any packages you are missing. It is set
to eval=FALSE so it does not run automatically. Run it
manually in the R console if you need to install packages for the first
time.
# Install duet from GitHub if you haven't already
# devtools::install_github("themisefth/duet")
# Core packages for this workflow
required_packages <- c(
"tidyverse", "cowplot", "rempsyc", "magick", "flextable", "remotes")
# Install any missing packages from the list
new_packages <- required_packages[!(required_packages %in% installed.packages()[,"Package"])]
if(length(new_packages)) install.packages(new_packages)
Load Libraries
library(tidyverse)
library(duet)
library(magick)
library(ggplot2)
library(cowplot)
library(rempsyc)
library(flextable)
# Set a consistent theme for ggplot2 plots
theme_set(theme_classic(base_size = 12))
Create Directory Structure
This will create the necessary folders in your project directory to
store raw data and outputs.
output_dirs <- c("./openpose_json", "./openpose_csv", "./figures")
for(dir_path in output_dirs) {
if (!dir.exists(dir_path)) dir.create(dir_path, recursive = TRUE)
}
2. Data Conversion: JSON to CSV
The first step is to convert the raw JSON files from OpenPose into a
single, tidy CSV file.
Understanding Your Data Structure
Place your raw OpenPose JSON files in a dedicated folder. For this
example, we assume they are in
./openpose_json/Individual_01/.
./openpose_json/
├── Individual_01/
│ ├── frame_000001_keypoints.json
│ ├── frame_000002_keypoints.json
│ └── ...
└── ...
Convert JSON to a Single Person CSV
This function reads all JSON files in the specified directory and
converts them into one CSV file. We set
export_type = 'person' and specify person = 1
to ensure we only process data for the first person detected by
OpenPose.
op_create_csv(
input_path = './openpose_json/Dyad_10',
output_path = './openpose_csv/Dyad_10',
model = 'body',
include_filename = TRUE,
export_type = 'dyad')
3. Data Import and Initial Checks
Import Data
Load the CSV file you just created.
# Read the CSV file into a dataframe
df <- read_csv('./openpose_csv/Dyad_10/Dyad-10.0_B_IDs-B013-B016_body_dyad.csv')
Check Data
It’s good practice to visually inspect a frame to ensure the data has
loaded correctly.
# Plot a single frame to check the pose
op_plot_openpose(df,
person = "both",
frame_num = 10,
lines = TRUE)

4. Data Quality and Preprocessing
Assess Data Quality
The op_plot_quality() function helps you understand the
completeness (how many keypoints were detected) and confidence
(OpenPose’s certainty) of your data over time.
# First, apply human-readable labels to the keypoints
df_labelled <- op_apply_keypoint_labels(df)
# Plot data completeness
p_completeness <- op_plot_quality(df_labelled,
plot_type = 'completeness',
threshold_line = 80) # 80% threshold line
# Plot tracking confidence
p_confidence <- op_plot_quality(df_labelled,
plot_type = 'confidence',
threshold_line = 80) # Confidence threshold of 80%
# Combine plots for a comprehensive view
p_quality_combined <- plot_grid(p_completeness, p_confidence, ncol = 1, labels = 'auto')
p_quality_combined

Summarise your data
At any stage you can produce summary statistics (mean, median, skew
etc) for your df
op_summarise(df)
Visualise Time Series
Plot the movement of specific keypoints over time to identify noise
or gaps.
# Plot the x and y coordinates for Nose (1) and Wrists (4, 7)
p_timeseries <- op_plot_timeseries(df,
free_y = TRUE, # Allow y-axes to have different scales
overlay = TRUE, # Overlay x and y on the same plot
max_facets = 20)
p_timeseries

Interpolate missing data
df_interp <- op_interpolate(df,
confidence_threshold = .3, # Set a confidence threshold of 80%
treat_na_conf_as_low = TRUE,
handle_zeros = TRUE,
handle_missing = TRUE)
p_timeseries_interp <- op_plot_timeseries(df_interp,
free_y = TRUE, # Allow y-axes to have different scales
overlay = TRUE, # Overlay x and y on the same plot
max_facets = 20)
p_timeseries_interp

Smooth Time Series Data
Use a moving average to smooth the data, which is often necessary
before computing velocity or acceleration. Here, we smooth the
x-coordinate of the Neck keypoint as an example.
# Apply moving-average smoothing and plot the result
op_smooth_timeseries(df,
keypoints = c('x1'), # Keypoint '1' is the Neck
method = 'rollmean',
rollmean_width = 5, # 5-frame rolling window
plot = TRUE,
side = 'right')

5. Kinematic Analysis
Now we can compute kinematic variables like velocity, acceleration,
and jerk.
Compute Velocity
Velocity is the rate of change of position. We compute it for all
keypoints.
# Display a sample of the velocity data
df_velocity %>%
filter(frame > 1 & frame < 6) %>%
select(person, frame, region, v_1, v_4, v_7) %>% # Select Neck and Wrists
nice_table()
Error in `select()`:
! Can't select columns that don't exist.
âś– Column `v_1` doesn't exist.
Run `]8;;x-r-run:rlang::last_trace()rlang::last_trace()]8;;` to see where the error occurred.
Compute Acceleration
Acceleration is the rate of change of velocity.
# We use the velocity dataframe as input
df_acceleration <- op_compute_acceleration(df_velocity,
merge_xy = TRUE,
overwrite = FALSE,
fps = 30)
# Display a sample of the velocity data
df_acceleration %>%
filter(frame > 1 & frame < 6) %>%
select(person, frame, region, a_1, a_4, a_7) %>% # Select Neck and Wrists
nice_table()
Error in `select()`:
! Can't select columns that don't exist.
âś– Column `a_1` doesn't exist.
Run `]8;;x-r-run:rlang::last_trace()rlang::last_trace()]8;;` to see where the error occurred.
Compute Jerk
Jerk (or jerkiness) is the rate of change of acceleration, often used
as a measure of movement smoothness.
base_filename | frame | region | person | v_Nose | v_Neck | v_RShoulder | v_RElbow | v_RWrist | v_LShoulder | v_LElbow | v_LWrist | v_MidHip | v_RHip | v_RKnee | v_RAnkle | v_LHip | v_LKnee | v_LAnkle | v_REye | v_LEye | v_REar | v_LEar | v_LBigToe | v_LSmallToe | v_LHeel | v_RBigToe | v_RSmallToe | v_RHeel | a_Nose | a_Neck | a_RShoulder | a_RElbow | a_RWrist | a_LShoulder | a_LElbow | a_LWrist | a_MidHip | a_RHip | a_RKnee | a_RAnkle | a_LHip | a_LKnee | a_LAnkle | a_REye | a_LEye | a_REar | a_LEar | a_LBigToe | a_LSmallToe | a_LHeel | a_RBigToe | a_RSmallToe | a_RHeel | j_Nose | j_Neck | j_RShoulder | j_RElbow | j_RWrist | j_LShoulder | j_LElbow | j_LWrist | j_MidHip | j_RHip | j_RKnee | j_RAnkle | j_LHip | j_LKnee | j_LAnkle | j_REye | j_LEye | j_REar | j_LEar | j_LBigToe | j_LSmallToe | j_LHeel | j_RBigToe | j_RSmallToe | j_RHeel |
|---|
Dyad-10.0_B_IDs-B013-B016 | 2.00 | body | left | 1.21 | 0.72 | 0.60 | 0.49 | 87.18 | 1.01 | 82.62 | 85.78 | 1.41 | 2.34 | 87.25 | 0.00 | 1.78 | 121.06 | 0.00 | 0.91 | 82.30 | 0.45 | 1.40 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dyad-10.0_B_IDs-B013-B016 | 3.00 | body | left | 0.79 | 84.30 | 0.98 | 0.27 | 0.60 | 1.88 | 0.19 | 0.64 | 1.02 | 1.42 | 1.39 | 0.00 | 0.79 | 85.86 | 0.00 | 0.75 | 1.06 | 84.96 | 1.15 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dyad-10.0_B_IDs-B013-B016 | 4.00 | body | left | 82.15 | 0.45 | 1.14 | 0.13 | 124.38 | 0.47 | 0.95 | 125.36 | 82.86 | 82.89 | 0.36 | 0.00 | 83.94 | 121.16 | 0.00 | 84.42 | 84.37 | 0.79 | 82.02 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 2,449.03 | 2,533.71 | 17.86 | 12.01 | 3,743.10 | 43.99 | 27.42 | 3,779.84 | 2,469.84 | 2,467.75 | 37.43 | 0.00 | 2,503.91 | 5,775.91 | 0.00 | 2,511.93 | 2,512.83 | 2,529.91 | 2,426.57 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dyad-10.0_B_IDs-B013-B016 | 5.00 | body | left | 2.67 | 0.36 | 0.26 | 1.15 | 85.35 | 0.50 | 0.03 | 86.47 | 83.28 | 83.91 | 0.86 | 0.00 | 84.27 | 0.51 | 0.00 | 81.25 | 2.14 | 2.35 | 84.48 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 2,390.43 | 17.93 | 32.72 | 38.45 | 5,812.82 | 26.10 | 27.96 | 2,598.35 | 4,984.21 | 5,004.15 | 27.91 | 0.00 | 5,046.30 | 3,633.77 | 0.00 | 3,466.80 | 2,479.50 | 50.43 | 3,548.01 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 145,183.22 | 75,857.54 | 1,252.52 | 1,497.71 | 283,073.53 | 1,139.13 | 1,654.95 | 178,184.35 | 223,617.91 | 224,148.51 | 314.87 | 0.00 | 226,504.35 | 279,091.58 | 0.00 | 166,385.28 | 149,769.48 | 77,409.17 | 165,809.13 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
Dyad-10.0_B_IDs-B013-B016 | 2.00 | body | right | 0.97 | 82.02 | 85.22 | 83.70 | 0.03 | 83.13 | 85.21 | 122.38 | 85.77 | 83.79 | 123.04 | 0.00 | 0.49 | 0.60 | 0.00 | 0.30 | 86.39 | 1.35 | 83.92 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 894,714.73 | 916,397.08 | 924,879.22 | 838,819.33 | 769,788.12 | 927,496.05 | 858,490.16 | 912,430.98 | 787,137.68 | 788,366.07 | 745,521.55 | 0.00 | 797,344.54 | 672,503.66 | 0.00 | 916,333.25 | 920,394.59 | 958,649.81 | 1,350,168.67 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 53,680,632.98 | 54,977,173.53 | 55,332,761.46 | 50,176,766.05 | 46,187,137.70 | 55,648,031.36 | 51,430,963.95 | 54,755,948.40 | 47,225,834.22 | 47,291,535.14 | 44,820,155.94 | 0.00 | 47,839,361.08 | 40,344,729.86 | 0.00 | 54,894,928.58 | 55,150,722.93 | 57,517,757.59 | 94,151,052.14 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
Dyad-10.0_B_IDs-B013-B016 | 3.00 | body | right | 1.11 | 0.35 | 0.67 | 0.47 | 0.61 | 82.53 | 1.62 | 85.30 | 85.77 | 1.17 | 2.84 | 0.00 | 82.80 | 82.80 | 0.00 | 0.31 | 85.80 | 1.26 | 2.04 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 39.73 | 2,455.22 | 2,538.48 | 2,502.00 | 18.20 | 4,969.80 | 2,548.72 | 5,781.79 | 5,146.27 | 2,536.60 | 3,744.94 | 0.00 | 2,475.17 | 2,485.05 | 0.00 | 3.60 | 3,597.71 | 19.39 | 2,463.21 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 26,841,093.39 | 27,497,578.39 | 27,822,205.89 | 25,239,483.72 | 23,094,167.96 | 27,829,502.08 | 25,830,992.34 | 27,444,591.52 | 23,617,951.30 | 23,654,593.06 | 22,277,543.57 | 0.00 | 23,846,083.76 | 20,168,284.19 | 0.00 | 27,489,989.71 | 27,683,459.81 | 28,758,979.27 | 40,518,940.73 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
Dyad-10.0_B_IDs-B013-B016 | 4.00 | body | right | 1.38 | 81.81 | 1.50 | 0.48 | 0.30 | 0.99 | 0.42 | 0.64 | 85.77 | 0.89 | 2.66 | 0.00 | 83.10 | 0.18 | 0.00 | 82.52 | 2.71 | 2.55 | 0.31 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 60.08 | 2,459.77 | 59.57 | 26.76 | 27.13 | 2,446.20 | 59.49 | 2,565.57 | 5,146.23 | 50.98 | 164.79 | 0.00 | 4,977.09 | 2,478.62 | 0.00 | 2,484.61 | 2,538.48 | 114.35 | 60.35 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 2,992.13 | 301.87 | 75,330.08 | 74,795.31 | 1,359.69 | 222,480.00 | 76,003.87 | 244,953.91 | 308,774.95 | 77,540.04 | 115,558.60 | 0.00 | 223,566.89 | 148,908.92 | 0.00 | 74,540.66 | 170,850.20 | 3,542.66 | 72,184.91 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
Dyad-10.0_B_IDs-B013-B016 | 5.00 | body | right | 116.89 | 1.12 | 86.25 | 0.09 | 0.21 | 0.31 | 84.30 | 1.20 | 0.60 | 0.52 | 0.67 | 0.00 | 83.10 | 83.16 | 0.00 | 1.61 | 86.94 | 84.95 | 83.40 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 3,465.30 | 2,421.90 | 2,543.40 | 17.10 | 11.53 | 28.46 | 2,520.12 | 54.48 | 2,573.16 | 37.80 | 98.85 | 0.00 | 4,986.00 | 2,500.22 | 0.00 | 2,439.08 | 2,535.70 | 2,480.47 | 2,493.02 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 102,504.54 | 146,449.00 | 74,713.39 | 1,297.41 | 1,088.40 | 72,576.50 | 75,099.32 | 77,233.99 | 231,581.52 | 2,621.23 | 7,904.13 | 0.00 | 298,891.58 | 843.51 | 0.00 | 147,707.00 | 105,616.41 | 71,396.17 | 75,356.49 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
Average kinematics overtime for analysis
6. Motion Energy Analysis
Motion energy is a measure of the total amount of movement,
calculated from the velocity of all keypoints.
# We use the 'df_jerk' dataframe as it contains all previously computed columns
# The function uses the velocity columns ('v_*') to compute motion energy
df_motion <- op_compute_motionenergy(df,
plot = TRUE)
# The resulting dataframe 'df_motion' now has a 'motion_energy' column
head(select(df_motion, frame, person, motion_energy))
7. Synchrony Analysis (Example for Dyadic Data)
Coherence analysis measures the synchrony between two time series
(e.g., the motion energy of two people). It is therefore typically used
for dyadic data. For this single-person tutorial, we will create a
dummy second person’s data just to demonstrate the
function’s syntax.
# Run coherance analysis for a single dyad
coherence_results <- op_compute_coherence(df_motion, plot = TRUE)
# View the coherence results summary
coherence_results
LS0tCnRpdGxlOiAnRHVldCBQYWNrYWdlOiBTaW5nbGUgUGVyc29uIEFuYWx5c2lzIFdvcmtmbG93JwphdXRob3I6ICJZb3VyIE5hbWUiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICB0aGVtZTogZmxhdGx5Ci0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBlY2hvID0gVFJVRSwKICB3YXJuaW5nID0gRkFMU0UsCiAgbWVzc2FnZSA9IEZBTFNFLAogIGZpZy53aWR0aCA9IDEwLAogIGZpZy5oZWlnaHQgPSA2LAogIGRwaSA9IDMwMAopCmBgYAoKIyBEdWV0IFBhY2thZ2UgVHV0b3JpYWw6IFNpbmdsZSBQZXJzb24gQW5hbHlzaXMKClRoaXMgdHV0b3JpYWwgZGVtb25zdHJhdGVzIGEgY29tcGxldGUgd29ya2Zsb3cgZm9yIGFuYWx5emluZyBPcGVuUG9zZSBkYXRhIGZyb20gYSAqKnNpbmdsZSBpbmRpdmlkdWFsKiogdXNpbmcgdGhlIGBkdWV0YCBSIHBhY2thZ2UuCgotLS0KCiMjIDEuIFNldHVwIGFuZCBJbnN0YWxsYXRpb24KCiMjIyBJbnN0YWxsIFJlcXVpcmVkIFBhY2thZ2VzCgpUaGUgY2h1bmsgYmVsb3cgd2lsbCBpbnN0YWxsIGFueSBwYWNrYWdlcyB5b3UgYXJlIG1pc3NpbmcuIEl0IGlzIHNldCB0byBgZXZhbD1GQUxTRWAgc28gaXQgZG9lcyBub3QgcnVuIGF1dG9tYXRpY2FsbHkuIFJ1biBpdCBtYW51YWxseSBpbiB0aGUgUiBjb25zb2xlIGlmIHlvdSBuZWVkIHRvIGluc3RhbGwgcGFja2FnZXMgZm9yIHRoZSBmaXJzdCB0aW1lLgoKYGBge3IgaW5zdGFsbC1wYWNrYWdlc30KIyBJbnN0YWxsIGR1ZXQgZnJvbSBHaXRIdWIgaWYgeW91IGhhdmVuJ3QgYWxyZWFkeQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigidGhlbWlzZWZ0aC9kdWV0IikKCiMgQ29yZSBwYWNrYWdlcyBmb3IgdGhpcyB3b3JrZmxvdwpyZXF1aXJlZF9wYWNrYWdlcyA8LSBjKAogICJ0aWR5dmVyc2UiLCAiY293cGxvdCIsICJyZW1wc3ljIiwgIm1hZ2ljayIsICJmbGV4dGFibGUiLCAicmVtb3RlcyIpCgojIEluc3RhbGwgYW55IG1pc3NpbmcgcGFja2FnZXMgZnJvbSB0aGUgbGlzdApuZXdfcGFja2FnZXMgPC0gcmVxdWlyZWRfcGFja2FnZXNbIShyZXF1aXJlZF9wYWNrYWdlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpWywiUGFja2FnZSJdKV0KaWYobGVuZ3RoKG5ld19wYWNrYWdlcykpIGluc3RhbGwucGFja2FnZXMobmV3X3BhY2thZ2VzKQpgYGAKCiMjIyBMb2FkIExpYnJhcmllcwoKYGBge3IgbG9hZC1saWJyYXJpZXN9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGR1ZXQpCmxpYnJhcnkobWFnaWNrKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY293cGxvdCkKbGlicmFyeShyZW1wc3ljKQpsaWJyYXJ5KGZsZXh0YWJsZSkKCiMgU2V0IGEgY29uc2lzdGVudCB0aGVtZSBmb3IgZ2dwbG90MiBwbG90cwp0aGVtZV9zZXQodGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxMikpCmBgYAoKIyMjIENyZWF0ZSBEaXJlY3RvcnkgU3RydWN0dXJlCgpUaGlzIHdpbGwgY3JlYXRlIHRoZSBuZWNlc3NhcnkgZm9sZGVycyBpbiB5b3VyIHByb2plY3QgZGlyZWN0b3J5IHRvIHN0b3JlIHJhdyBkYXRhIGFuZCBvdXRwdXRzLgoKYGBge3IgY3JlYXRlLWRpcmVjdG9yaWVzfQpvdXRwdXRfZGlycyA8LSBjKCIuL29wZW5wb3NlX2pzb24iLCAiLi9vcGVucG9zZV9jc3YiLCAiLi9maWd1cmVzIikKZm9yKGRpcl9wYXRoIGluIG91dHB1dF9kaXJzKSB7CiAgaWYgKCFkaXIuZXhpc3RzKGRpcl9wYXRoKSkgZGlyLmNyZWF0ZShkaXJfcGF0aCwgcmVjdXJzaXZlID0gVFJVRSkKfQpgYGAKCi0tLQoKIyMgMi4gRGF0YSBDb252ZXJzaW9uOiBKU09OIHRvIENTVgoKVGhlIGZpcnN0IHN0ZXAgaXMgdG8gY29udmVydCB0aGUgcmF3IEpTT04gZmlsZXMgZnJvbSBPcGVuUG9zZSBpbnRvIGEgc2luZ2xlLCB0aWR5IENTViBmaWxlLgoKIyMjIFVuZGVyc3RhbmRpbmcgWW91ciBEYXRhIFN0cnVjdHVyZQoKUGxhY2UgeW91ciByYXcgT3BlblBvc2UgSlNPTiBmaWxlcyBpbiBhIGRlZGljYXRlZCBmb2xkZXIuIEZvciB0aGlzIGV4YW1wbGUsIHdlIGFzc3VtZSB0aGV5IGFyZSBpbiBgLi9vcGVucG9zZV9qc29uL0luZGl2aWR1YWxfMDEvYC4KCmBgYHRleHQKLi9vcGVucG9zZV9qc29uLwrilJzilIDilIAgSW5kaXZpZHVhbF8wMS8K4pSCICAg4pSc4pSA4pSAIGZyYW1lXzAwMDAwMV9rZXlwb2ludHMuanNvbgrilIIgICDilJzilIDilIAgZnJhbWVfMDAwMDAyX2tleXBvaW50cy5qc29uCuKUgiAgIOKUlOKUgOKUgCAuLi4K4pSU4pSA4pSAIC4uLgpgYGAKCiMjIyBDb252ZXJ0IEpTT04gdG8gYSBTaW5nbGUgUGVyc29uIENTVgoKVGhpcyBmdW5jdGlvbiByZWFkcyBhbGwgSlNPTiBmaWxlcyBpbiB0aGUgc3BlY2lmaWVkIGRpcmVjdG9yeSBhbmQgY29udmVydHMgdGhlbSBpbnRvIG9uZSBDU1YgZmlsZS4gV2Ugc2V0IGBleHBvcnRfdHlwZSA9ICdwZXJzb24nYCBhbmQgc3BlY2lmeSBgcGVyc29uID0gMWAgdG8gZW5zdXJlIHdlIG9ubHkgcHJvY2VzcyBkYXRhIGZvciB0aGUgZmlyc3QgcGVyc29uIGRldGVjdGVkIGJ5IE9wZW5Qb3NlLgoKYGBge3IgY29udmVydC1zaW5nbGUtZHlhZH0Kb3BfY3JlYXRlX2NzdigKICBpbnB1dF9wYXRoID0gJy4vb3BlbnBvc2VfanNvbi9EeWFkXzEwJywKICBvdXRwdXRfcGF0aCA9ICcuL29wZW5wb3NlX2Nzdi9EeWFkXzEwJywKICBtb2RlbCA9ICdib2R5JywKICBpbmNsdWRlX2ZpbGVuYW1lID0gVFJVRSwKICBleHBvcnRfdHlwZSA9ICdkeWFkJykKYGBgCgotLS0KCiMjIDMuIERhdGEgSW1wb3J0IGFuZCBJbml0aWFsIENoZWNrcwoKIyMjIEltcG9ydCBEYXRhCgpMb2FkIHRoZSBDU1YgZmlsZSB5b3UganVzdCBjcmVhdGVkLgoKYGBge3IgaW1wb3J0LWRhdGF9CiMgUmVhZCB0aGUgQ1NWIGZpbGUgaW50byBhIGRhdGFmcmFtZQpkZiA8LSByZWFkX2NzdignLi9vcGVucG9zZV9jc3YvRHlhZF8xMC9EeWFkLTEwLjBfQl9JRHMtQjAxMy1CMDE2X2JvZHlfZHlhZC5jc3YnKQpgYGAKCiMjIyBDaGVjayBEYXRhCgpJdCdzIGdvb2QgcHJhY3RpY2UgdG8gdmlzdWFsbHkgaW5zcGVjdCBhIGZyYW1lIHRvIGVuc3VyZSB0aGUgZGF0YSBoYXMgbG9hZGVkIGNvcnJlY3RseS4KCmBgYHtyIGNoZWNrLWRhdGF9CiMgUGxvdCBhIHNpbmdsZSBmcmFtZSB0byBjaGVjayB0aGUgcG9zZQpvcF9wbG90X29wZW5wb3NlKGRmLCAKICAgICAgICAgICAgICAgICBwZXJzb24gPSAiYm90aCIsCiAgICAgICAgICAgICAgICAgZnJhbWVfbnVtID0gMTAsIAogICAgICAgICAgICAgICAgIGxpbmVzID0gVFJVRSkKYGBgCgotLS0KCiMjIDQuIERhdGEgUXVhbGl0eSBhbmQgUHJlcHJvY2Vzc2luZwoKIyMjIEFzc2VzcyBEYXRhIFF1YWxpdHkKClRoZSBgb3BfcGxvdF9xdWFsaXR5KClgIGZ1bmN0aW9uIGhlbHBzIHlvdSB1bmRlcnN0YW5kIHRoZSBjb21wbGV0ZW5lc3MgKGhvdyBtYW55IGtleXBvaW50cyB3ZXJlIGRldGVjdGVkKSBhbmQgY29uZmlkZW5jZSAoT3BlblBvc2UncyBjZXJ0YWludHkpIG9mIHlvdXIgZGF0YSBvdmVyIHRpbWUuCgpgYGB7ciBwbG90LXF1YWxpdHl9CiMgRmlyc3QsIGFwcGx5IGh1bWFuLXJlYWRhYmxlIGxhYmVscyB0byB0aGUga2V5cG9pbnRzCmRmX2xhYmVsbGVkIDwtIG9wX2FwcGx5X2tleXBvaW50X2xhYmVscyhkZikKCiMgUGxvdCBkYXRhIGNvbXBsZXRlbmVzcwpwX2NvbXBsZXRlbmVzcyA8LSBvcF9wbG90X3F1YWxpdHkoZGZfbGFiZWxsZWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF90eXBlID0gJ2NvbXBsZXRlbmVzcycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJlc2hvbGRfbGluZSA9IDgwKSAjIDgwJSB0aHJlc2hvbGQgbGluZQoKIyBQbG90IHRyYWNraW5nIGNvbmZpZGVuY2UKcF9jb25maWRlbmNlIDwtIG9wX3Bsb3RfcXVhbGl0eShkZl9sYWJlbGxlZCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF90eXBlID0gJ2NvbmZpZGVuY2UnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZF9saW5lID0gODApICMgQ29uZmlkZW5jZSB0aHJlc2hvbGQgb2YgODAlCgojIENvbWJpbmUgcGxvdHMgZm9yIGEgY29tcHJlaGVuc2l2ZSB2aWV3CnBfcXVhbGl0eV9jb21iaW5lZCA8LSBwbG90X2dyaWQocF9jb21wbGV0ZW5lc3MsIHBfY29uZmlkZW5jZSwgbmNvbCA9IDEsIGxhYmVscyA9ICdhdXRvJykKCnBfcXVhbGl0eV9jb21iaW5lZApgYGAKCiMjIyBTdW1tYXJpc2UgeW91ciBkYXRhCgpBdCBhbnkgc3RhZ2UgeW91IGNhbiBwcm9kdWNlIHN1bW1hcnkgc3RhdGlzdGljcyAobWVhbiwgbWVkaWFuLCBza2V3IGV0YykgZm9yIHlvdXIgZGYKCmBgYHtyfQpvcF9zdW1tYXJpc2UoZGYpCmBgYAoKIyMjIFZpc3VhbGlzZSBUaW1lIFNlcmllcwoKUGxvdCB0aGUgbW92ZW1lbnQgb2Ygc3BlY2lmaWMga2V5cG9pbnRzIG92ZXIgdGltZSB0byBpZGVudGlmeSBub2lzZSBvciBnYXBzLgoKYGBge3IgcGxvdC10aW1lc2VyaWVzfQojIFBsb3QgdGhlIHggYW5kIHkgY29vcmRpbmF0ZXMgZm9yIE5vc2UgKDEpIGFuZCBXcmlzdHMgKDQsIDcpCnBfdGltZXNlcmllcyA8LSBvcF9wbG90X3RpbWVzZXJpZXMoZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyZWVfeSA9IFRSVUUsICMgQWxsb3cgeS1heGVzIHRvIGhhdmUgZGlmZmVyZW50IHNjYWxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG92ZXJsYXkgPSBUUlVFLCAjIE92ZXJsYXkgeCBhbmQgeSBvbiB0aGUgc2FtZSBwbG90CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2ZhY2V0cyA9IDIwKSAKCnBfdGltZXNlcmllcwpgYGAKCiMjIyBJbnRlcnBvbGF0ZSBtaXNzaW5nIGRhdGEKCmBgYHtyfQpkZl9pbnRlcnAgPC0gb3BfaW50ZXJwb2xhdGUoZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uZmlkZW5jZV90aHJlc2hvbGQgPSAuMywgIyBTZXQgYSBjb25maWRlbmNlIHRocmVzaG9sZCBvZiA4MCUKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyZWF0X25hX2NvbmZfYXNfbG93ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhhbmRsZV96ZXJvcyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBoYW5kbGVfbWlzc2luZyA9IFRSVUUpCgpwX3RpbWVzZXJpZXNfaW50ZXJwIDwtIG9wX3Bsb3RfdGltZXNlcmllcyhkZl9pbnRlcnAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyZWVfeSA9IFRSVUUsICMgQWxsb3cgeS1heGVzIHRvIGhhdmUgZGlmZmVyZW50IHNjYWxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG92ZXJsYXkgPSBUUlVFLCAjIE92ZXJsYXkgeCBhbmQgeSBvbiB0aGUgc2FtZSBwbG90CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2ZhY2V0cyA9IDIwKSAKCnBfdGltZXNlcmllc19pbnRlcnAKYGBgCgoKIyMjIFNtb290aCBUaW1lIFNlcmllcyBEYXRhCgpVc2UgYSBtb3ZpbmcgYXZlcmFnZSB0byBzbW9vdGggdGhlIGRhdGEsIHdoaWNoIGlzIG9mdGVuIG5lY2Vzc2FyeSBiZWZvcmUgY29tcHV0aW5nIHZlbG9jaXR5IG9yIGFjY2VsZXJhdGlvbi4gSGVyZSwgd2Ugc21vb3RoIHRoZSB4LWNvb3JkaW5hdGUgb2YgdGhlIGBOZWNrYCBrZXlwb2ludCBhcyBhbiBleGFtcGxlLgoKYGBge3Igc21vb3RoLWRhdGF9CiMgQXBwbHkgbW92aW5nLWF2ZXJhZ2Ugc21vb3RoaW5nIGFuZCBwbG90IHRoZSByZXN1bHQKb3Bfc21vb3RoX3RpbWVzZXJpZXMoZGYsIAogICAgICAgICAgICAgICAgICAgICBrZXlwb2ludHMgPSBjKCd4MScpLCAjIEtleXBvaW50ICcxJyBpcyB0aGUgTmVjawogICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAncm9sbG1lYW4nLAogICAgICAgICAgICAgICAgICAgICByb2xsbWVhbl93aWR0aCA9IDUsICMgNS1mcmFtZSByb2xsaW5nIHdpbmRvdwogICAgICAgICAgICAgICAgICAgICBwbG90ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgc2lkZSA9ICdyaWdodCcpCmBgYAoKLS0tCgojIyA1LiBLaW5lbWF0aWMgQW5hbHlzaXMKCk5vdyB3ZSBjYW4gY29tcHV0ZSBraW5lbWF0aWMgdmFyaWFibGVzIGxpa2UgdmVsb2NpdHksIGFjY2VsZXJhdGlvbiwgYW5kIGplcmsuCgojIyMgQ29tcHV0ZSBWZWxvY2l0eQoKVmVsb2NpdHkgaXMgdGhlIHJhdGUgb2YgY2hhbmdlIG9mIHBvc2l0aW9uLiBXZSBjb21wdXRlIGl0IGZvciBhbGwga2V5cG9pbnRzLgoKYGBge3IgY29tcHV0ZS12ZWxvY2l0eX0KZGYgPC0gb3BfYXBwbHlfa2V5cG9pbnRfbGFiZWxzKGRmKQoKIyBDb21wdXRlIHZlbG9jaXR5IGFuZCBhZGQgaXQgdG8gdGhlIGRhdGFmcmFtZQpkZl92ZWxvY2l0eSA8LSBvcF9jb21wdXRlX3ZlbG9jaXR5KGRmLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXJnZV94eSA9IFRSVUUsICMgQ29tYmluZSB4IGFuZCB5IHZlbG9jaXR5CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3ZlcndyaXRlID0gRkFMU0UsICMgVGhpcyBkZWxldGVzIHlvdXIgb3JpZ2luYWwgY29sdW1ucyBmb3IgYSB0aWRpZXIgZGF0YWZyYW1lLCByZXRhaW4gaWYgeW91IHBsYW4gdG8gY29tcHV0ZSBhY2NlbGVyYXRpb24gYW5kIGplcmsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcHMgPSAzMCkgIyBZb3VyIHZpZGVvJ3MgZnJhbWUgcmF0ZQoKIyBEaXNwbGF5IGEgc2FtcGxlIG9mIHRoZSB2ZWxvY2l0eSBkYXRhCmRmX3ZlbG9jaXR5ICU+JQogIGZpbHRlcihmcmFtZSA+IDEgJiBmcmFtZSA8IDYpICU+JQogIG5pY2VfdGFibGUoKQpgYGAKCiMjIyBDb21wdXRlIEFjY2VsZXJhdGlvbgoKQWNjZWxlcmF0aW9uIGlzIHRoZSByYXRlIG9mIGNoYW5nZSBvZiB2ZWxvY2l0eS4KCmBgYHtyIGNvbXB1dGUtYWNjZWxlcmF0aW9ufQojIFdlIHVzZSB0aGUgdmVsb2NpdHkgZGF0YWZyYW1lIGFzIGlucHV0CmRmX2FjY2VsZXJhdGlvbiA8LSBvcF9jb21wdXRlX2FjY2VsZXJhdGlvbihkZl92ZWxvY2l0eSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXJnZV94eSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdmVyd3JpdGUgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZwcyA9IDMwKQoKIyBEaXNwbGF5IGEgc2FtcGxlIG9mIHRoZSB2ZWxvY2l0eSBkYXRhCmRmX2FjY2VsZXJhdGlvbiAlPiUKICBmaWx0ZXIoZnJhbWUgPiAxICYgZnJhbWUgPCA2KSAlPiUKICBuaWNlX3RhYmxlKCkKYGBgCgojIyMgQ29tcHV0ZSBKZXJrCgpKZXJrIChvciBqZXJraW5lc3MpIGlzIHRoZSByYXRlIG9mIGNoYW5nZSBvZiBhY2NlbGVyYXRpb24sIG9mdGVuIHVzZWQgYXMgYSBtZWFzdXJlIG9mIG1vdmVtZW50IHNtb290aG5lc3MuCgpgYGB7ciBjb21wdXRlLWplcmt9CiMgV2UgdXNlIHRoZSBhY2NlbGVyYXRpb24gZGF0YWZyYW1lIGFzIGlucHV0CmRmX2plcmsgPC0gb3BfY29tcHV0ZV9qZXJrKGRmX2FjY2VsZXJhdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVyZ2VfeHkgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICBvdmVyd3JpdGUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICBmcHMgPSAzMCkKCiMgRGlzcGxheSBhIHNhbXBsZSBvZiB0aGUgdmVsb2NpdHkgZGF0YQpkZl9qZXJrICU+JQogIGZpbHRlcihmcmFtZSA+IDEgJiBmcmFtZSA8IDYpICU+JQogIG5pY2VfdGFibGUoKQpgYGAKCiMjIyBBdmVyYWdlIGtpbmVtYXRpY3Mgb3ZlcnRpbWUgZm9yIGFuYWx5c2lzCgpgYGB7ciBhdmVyYWdlLWtpbmVtYXRpY3N9CiMgQWdncmVnYXRlIHJlc3VsdHMgdG8gYm9keSBwYXJ0cyBhbmQgdGhlbiB0byBvdmVyYWxsIG1lYXN1cmVzCmtpbmVtYXRpY3Nfc3VtbWFyeV9iYXRjaCA8LSBkZl9qZXJrICU+JQogIHNlbGVjdChiYXNlX2ZpbGVuYW1lLCBwZXJzb24sIHN0YXJ0c193aXRoKCJ2XyIpLCBzdGFydHNfd2l0aCgiYV8iKSwgc3RhcnRzX3dpdGgoImpfIikpICU+JQogIGdyb3VwX2J5KGJhc2VfZmlsZW5hbWUsIHBlcnNvbikgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcyhldmVyeXRoaW5nKCksIH5tZWFuKC54LCBuYS5ybSA9IFRSVUUpKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgcm93d2lzZSgpICU+JQogIHRyYW5zbXV0ZSgKICAgIGJhc2VfZmlsZW5hbWUsIHBlcnNvbiwgCiAgICB2X2hlYWQgPSBtZWFuKGModl9Ob3NlLCB2X1JFeWUsIHZfTEV5ZSksIG5hLnJtID0gVFJVRSksCiAgICBhX2hlYWQgPSBtZWFuKGMoYV9Ob3NlLCBhX1JFeWUsIGFfTEV5ZSksIG5hLnJtID0gVFJVRSksCiAgICBqX2hlYWQgPSBtZWFuKGMoal9Ob3NlLCBqX1JFeWUsIGpfTEV5ZSksIG5hLnJtID0gVFJVRSksCiAgICB2X2FybXMgPSBtZWFuKGModl9SV3Jpc3QsIHZfUkVsYm93LCB2X0xXcmlzdCwgdl9MRWxib3cpLCBuYS5ybSA9IFRSVUUpLAogICAgYV9hcm1zID0gbWVhbihjKGFfUldyaXN0LCBhX1JFbGJvdywgYV9MV3Jpc3QsIGFfTEVsYm93KSwgbmEucm0gPSBUUlVFKSwKICAgIGpfYXJtcyA9IG1lYW4oYyhqX1JXcmlzdCwgal9SRWxib3csIGpfTFdyaXN0LCBqX0xFbGJvdyksIG5hLnJtID0gVFJVRSksCiAgICB2ZWxvY2l0eV90b3RhbCA9IG1lYW4oYyh2X2hlYWQsIHZfYXJtcyksIG5hLnJtID0gVFJVRSksCiAgICBhY2NlbGVyYXRpb25fdG90YWwgPSBtZWFuKGMoYV9oZWFkLCBhX2FybXMpLCBuYS5ybSA9IFRSVUUpLAogICAgamVya190b3RhbCA9IG1lYW4oYyhqX2hlYWQsIGpfYXJtcyksIG5hLnJtID0gVFJVRSkKICApICU+JQogIHVuZ3JvdXAoKQoKa2luZW1hdGljc19zdW1tYXJ5X2JhdGNoCgojIFlvdSBjYW4gbm93IHNhdmUgdGhpcyBmaWxlIGZvciBmdXJ0aGVyIGFuYWx5c2lzCmBgYAoKLS0tCgojIyA2LiBNb3Rpb24gRW5lcmd5IEFuYWx5c2lzCgpNb3Rpb24gZW5lcmd5IGlzIGEgbWVhc3VyZSBvZiB0aGUgdG90YWwgYW1vdW50IG9mIG1vdmVtZW50LCBjYWxjdWxhdGVkIGZyb20gdGhlIHZlbG9jaXR5IG9mIGFsbCBrZXlwb2ludHMuCgpgYGB7ciBjb21wdXRlLW1vdGlvbi1lbmVyZ3l9CiMgV2UgdXNlIHRoZSAnZGZfamVyaycgZGF0YWZyYW1lIGFzIGl0IGNvbnRhaW5zIGFsbCBwcmV2aW91c2x5IGNvbXB1dGVkIGNvbHVtbnMKIyBUaGUgZnVuY3Rpb24gdXNlcyB0aGUgdmVsb2NpdHkgY29sdW1ucyAoJ3ZfKicpIHRvIGNvbXB1dGUgbW90aW9uIGVuZXJneQpkZl9tb3Rpb24gPC0gb3BfY29tcHV0ZV9tb3Rpb25lbmVyZ3koZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdCA9IFRSVUUpCgojIFRoZSByZXN1bHRpbmcgZGF0YWZyYW1lICdkZl9tb3Rpb24nIG5vdyBoYXMgYSAnbW90aW9uX2VuZXJneScgY29sdW1uCmhlYWQoc2VsZWN0KGRmX21vdGlvbiwgZnJhbWUsIHBlcnNvbiwgbW90aW9uX2VuZXJneSkpCmBgYAoKLS0tCgojIyA3LiBTeW5jaHJvbnkgQW5hbHlzaXMgKEV4YW1wbGUgZm9yIER5YWRpYyBEYXRhKQoKQ29oZXJlbmNlIGFuYWx5c2lzIG1lYXN1cmVzIHRoZSBzeW5jaHJvbnkgYmV0d2VlbiB0d28gdGltZSBzZXJpZXMgKGUuZy4sIHRoZSBtb3Rpb24gZW5lcmd5IG9mIHR3byBwZW9wbGUpLiBJdCBpcyB0aGVyZWZvcmUgdHlwaWNhbGx5IHVzZWQgZm9yIGR5YWRpYyBkYXRhLiBGb3IgdGhpcyBzaW5nbGUtcGVyc29uIHR1dG9yaWFsLCB3ZSB3aWxsIGNyZWF0ZSBhICoqZHVtbXkgc2Vjb25kIHBlcnNvbidzIGRhdGEqKiBqdXN0IHRvIGRlbW9uc3RyYXRlIHRoZSBmdW5jdGlvbidzIHN5bnRheC4KCmBgYHtyIGNvbXB1dGUtY29oZXJlbmNlfQojIFJ1biBjb2hlcmFuY2UgYW5hbHlzaXMgZm9yIGEgc2luZ2xlIGR5YWQKY29oZXJlbmNlX3Jlc3VsdHMgPC0gb3BfY29tcHV0ZV9jb2hlcmVuY2UoZGZfbW90aW9uLCBwbG90ID0gVFJVRSkKYGBgCgpgYGB7ciBvdXRwdXQtY29oZXJlbmNlfQojIFZpZXcgdGhlIGNvaGVyZW5jZSByZXN1bHRzIHN1bW1hcnkKY29oZXJlbmNlX3Jlc3VsdHMKYGBgCgotLS0KCiMjIFNlc3Npb24gSW5mb3JtYXRpb24KClRoaXMgY2h1bmsgZG9jdW1lbnRzIHRoZSBSIHZlcnNpb24gYW5kIGxvYWRlZCBwYWNrYWdlcywgd2hpY2ggaXMgY3J1Y2lhbCBmb3IgcmVwcm9kdWNpYmlsaXR5LgoKYGBge3Igc2Vzc2lvbi1pbmZvfQojIERvY3VtZW50IHlvdXIgY29tcHV0YXRpb25hbCBlbnZpcm9ubWVudApzZXNzaW9uSW5mbygpCg==